import * as AdmZip from 'adm-zip';
import * as fs from 'fs';
import { FSUtils, Fs, Path } from './fsutils';
import { CommonUtils } from './commonutils';
import { HttpUtils } from './httputils';

const GBK_ENCODING = 'GBK';
//#region 废弃代码(Adm-zip实现)
// export class Zip {
//     zip: AdmZip;
//     /**
//      * 构造函数
//      * @param filepath zip文件路径
//      */
//     constructor(filepath?: string) {
//         if (typeof(filepath) === 'undefined' || filepath === '') {
//             this.zip = new AdmZip();
//         } else {
//             this.zip = new AdmZip(filepath);
//         }
//     }
//     /**
//      * 添加文件到zip文件
//      * @param entryName zip条目名称，如：dir1/dir2/file1.txt或file2.json
//      * @param data 文件数据
//      * @param comment 注释信息
//      * @param attr 属性
//      */
//     addFile(entryName: string, data: Buffer, comment: string, attr?: number): void {
//         this.zip.addFile(entryName, data, comment, attr);
//     }
//     /**
//      * 添加本地文件到zip文件
//      * @param localPath 本地文件路径
//      * @param zipPath 添加zip文件内的路径,如果为空则添加到根目录，否则添加到指定目录下,如：dir1或dir1/dir2
//      * @param zipName 添加到zip文件内的文件名称，如果为空则为原始名称
//      */
//     addLocalFile(localPath: string, zipPath?: string, zipName?: string): void {
//         this.zip.addLocalFile(localPath, zipPath, zipName);
//     }
//     /**
//      * 添加本地目录到zip文件
//      * @param localPath 本地目录的路径
//      * @param zipPath 添加zip文件内的路径,如果为空则添加到根目录，否则添加到指定目录下,如：dir1或dir1/dir2
//      * @param filter 过滤文件选项,正则表达式或函数((file:string) => boolean)
//      */
//     addLocalFolder(localPath: string, zipPath?: string, filter?: Function | Function): void {
//         this.zip.addLocalFolder(localPath, zipPath, filter);
//     }
//     /**
//      * 添加zip文件的注释信息
//      * @param comment 注释信息
//      */
//     addZipComment(comment: string): void {
//         this.zip.addZipComment(comment);
//     }
//     /**
//      * 添加zip文件指定条目的注释信息
//      * @param entry zip条目(zip条目路径字符串(如:dir1/dir2/file.txt)或zip条目对象)
//      * @param comment 注释信息
//      */
//     addZipEntryComment(entry: ZipEntry | string, comment: string): void {
//         this.zip.addZipEntryComment(entry, comment);
//     }
//     /**
//      * 获取zip条目注释信息
//      * @param entry zip条目(zip条目路径字符串(如:dir1/dir2/file.txt)或zip条目对象)
//      */
//     getZipEntryComment(entry: ZipEntry | string): string {
//         return this.zip.getZipEntryComment(entry);
//     }
//     /**
//      * 解压zip文件到指定目录
//      * @param targetPath 目标目录
//      * @param overwrite 标识是否覆盖存在的文件,默认false不覆盖存在的文件
//      */
//     extractAllTo(targetPath: string, overwrite?: boolean): void {
//         this.zip.extractAllTo(targetPath, overwrite);
//     }
//     /**
//      * 解压zip文件到指定目录
//      * @param targetPath 目标目录
//      * @param callback 解压回调处理
//      * @param overwrite 标识是否覆盖存在的文件,默认false不覆盖存在的文件
//      */
//     extractAllToAsync(targetPath: string, callback?: (error: any) => void, overwrite?: boolean): void {
//         try {
//             this.zip.extractAllToAsync(targetPath, overwrite, callback);
//         } catch (err) {
//             if (callback) {
//                 callback(err);
//             }
//         }
//     }
//     /**
//      * 解压指定条目到指定目录
//      * @param entry zip条目(zip条目路径字符串(如:dir1/dir2/file.txt)或zip条目对象)
//      * @param targetPath 目标目录
//      * @param maintainEntryPath 标识是否保持条目中的路径,默认为：true
//      * @param overwrite 标识是否覆盖存在的文件,默认false不覆盖存在的文件
//      */
//     extractEntryTo(entry: ZipEntry | string, targetPath: string, maintainEntryPath?: boolean, overwrite?: boolean): boolean {
//         return this.zip.extractEntryTo(entry, targetPath, maintainEntryPath, overwrite);
//     }
//     /**
//      * 读取指定zip条目的文本数据
//      * @param entry zip条目(zip条目路径字符串(如:dir1/dir2/file.txt)或zip条目对象)
//      * @param encoding 文本编码，默认：utf8
//      */
//     readAsText(entry: ZipEntry | string, encoding?: string): string {
//         return this.zip.readAsText(entry, encoding);
//     }
//     /**
//      * 异步读取指定zip条目的文本数据
//      * @param entry zip条目(zip条目路径字符串(如:dir1/dir2/file.txt)或zip条目对象)
//      * @param callback 回调处理
//      * @param encoding 文本编码，默认：utf8
//      */
//     readAsTextAsync(entry: ZipEntry | string, callback: (text: string) => void, encoding?: string): string {
//         return this.zip.readAsTextAsync(entry, callback, encoding);
//     }
//     /**
//      * 读取指定zip条目的二进制数据
//      * @param entry zip条目(zip条目路径字符串(如:dir1/dir2/file.txt)或zip条目对象)
//      */
//     readFile(entry: ZipEntry | string): Buffer {
//         return this.zip.readFile(entry);
//     }
//     /**
//      * 异步读取指定zip条目的二进制数据
//      * @param entry zip条目(zip条目路径字符串(如:dir1/dir2/file.txt)或zip条目对象)
//      * @param callback 回调处理
//      */
//     readFileAsync(entry: ZipEntry | string, callback: (data: Buffer) => void): void {
//         return this.zip.readFileAsync(entry, callback);
//     }
//     /**获取所有的zip条目信息 */
//     getEntries(): ZipEntry[] {
//         return this.zip.getEntries();
//     }
//     /**
//      * 获取指定名称的zip条目
//      * @param entryName zip条目名称，如：dir1/dir2/file1.txt或file2.json
//      */
//     getEntry(entryName: string): ZipEntry {
//         return this.zip.getEntry(entryName);
//     }
//     /**
//      * 获取zip文件注释信息
//      */
//     getZipComment(): string {
//         return this.zip.getZipComment();
//     }
//     /**
//      * 测试是否为zip文件
//      */
//     test(): boolean {
//         return this.zip.test();
//     }
//     /**
//      * 获取二进制数据
//      */
//     toBuffer(): Buffer {
//         return this.zip.toBuffer(null, null, null, null);
//     }
//     /**
//      * 异步获取二进制数据
//      */
//     toBufferAsync(onSuccess: (data: Buffer) => void, onFail?: (error: any) => void
//     , onItemStart?: (name: string) => void, onItemEnd?: (name: string) => void): void {
//         if (!onSuccess) {
//             throw new Error('onSuccess不能为空');
//         }
//         try {
//             return this.zip.toBuffer(onSuccess, onFail, onItemStart, onItemEnd);
//         } catch (ex) {
//             if (onFail) {
//                 onFail(ex);
//             }
//         }
//     }
//     /**
//      * 更新zip条目的数据
//      * @param entry zip条目(zip条目路径字符串(如:dir1/dir2/file.txt)或zip条目对象)
//      * @param data 二进制数据
//      */
//     updateEntry(entry: ZipEntry | string, data: Buffer) {
//         this.zip.updateFile(entry);
//     }
//     /**
//      * 删除zip条目
//      * @param entry zip条目(zip条目路径字符串(如:dir1/dir2/file.txt)或zip条目对象)
//      */
//     deleteEntry(entry: ZipEntry | string) {
//         this.zip.deleteFile(entry);
//     }
//     /**
//      * 保存当前zip文件
//      * @param callback 回调处理
//      */
//     save(callback?: (error: any) => void) {
//         try {
//             this.zip.writeZip(callback);
//         } catch (err) {
//             if (callback) {
//                 callback(err);
//             }
//         }
//     }
//     /**
//      * 另存zip文件
//      * @param targetFileName 另存的zip文件路径
//      * @param callback 回调处理
//      */
//     saveAs(targetFileName: string, callback?: (error: any) => void) {
//         try {
//             this.zip.writeZip(targetFileName, callback);
//         } catch (err) {
//             if (callback) {
//                 callback(err);
//             }
//         }
//     }
// }

// /**zip条目类 */
// export interface ZipEntry {
//     /**zip条目基本名称 */
//     readonly name: string;
//     /**标识zip条目是否为目录 */
//     readonly isDirectory: boolean;
//     /**zip条目属性 */
//     attr: number;
//     /**zip条目名称 */
//     entryName: string;
//     /**zip条目额外数据 */
//     extra: Buffer;
//     /**zip条目注释信息 */
//     comment: string;
//     /**zip条目头数据 */
//     header: any;
//     rawEntryName: Buffer;
//     /**获取压缩数据 */
//     getCompressedData(): Buffer;
//     /**异步获取压缩数据 */
//     getCompressedDataAsync(callback: (value: Buffer) => void);
//     getData(pass: string);
//     getDataAsync(callback: (value: Buffer) => void, pass: string);
//     setData(value: Buffer);
//     packHeader(): Buffer;
//     toString(): string;
// }
//#endregion

export class JSZipWrapper {
    private zipFile: string;
    private zip: any;
    /**
     * 构造函数
     * @param zipFile zip文件路径，如果为则新建zip文件
     * @param loadedCallback zip文件加载完成时的回调处理，zipFile参数必须设置
     */
    constructor(zipFile?: string, loadedCallback?: (error: any, self: JSZipWrapper) => void) {
        const JSZip = require('jszip');
        if (zipFile) {
            const _loadedCallback = (error, zip) => {
                if (loadedCallback) {
                    loadedCallback(error, zip);
                }
            };
            const options = {
                createFolders: true,
                decodeFileName: function (bytes) {
                    return CommonUtils.decodeUseIconv(bytes, GBK_ENCODING);
                }
            };
            if (!FSUtils.isHttp(zipFile)) {
                JSZip.loadAsync(FSUtils.readLocalFile(zipFile), options)
                .then(zip => {
                    this.zip = zip;
                    this.zipFile = zipFile;
                    _loadedCallback(null, this);
                })
                .catch(error => {
                    _loadedCallback(error, null);
                });
            } else {
                JSZip.loadAsync(HttpUtils.readHttpFile(zipFile), options)
                .then(zip => {
                    this.zip = zip;
                    const {remote} = require('electron');
                    let tempDir = remote.app.getPath('temp');
                    tempDir = !tempDir.endsWith('\\') ? `${tempDir}\\` : tempDir;
                    this.zipFile = `${tempDir}${CommonUtils.guid()}.zip`;
                    // console.log(this.zipFile);
                    // this.save();
                    _loadedCallback(null, this);
                })
                .catch(error => {
                    _loadedCallback(error, null);
                });
            }
        } else {
            this.zip = new JSZip();
        }
    }
    /**
     * 添加文本内容到zip
     * @param text 要加入的文本
     * @param zipEntryName zip条目名称，如：dir1/dir2/file1.txt或file2.json
     */
    addText(text: string, zipEntryName: string) {
        this.zip.file(zipEntryName, text);
    }
    /**
     * 添加本地文件
     * @param localPath 本地文件路径
     * @param zipPath zip文件中的路径,如果为空则为根目录，如:dir1/dir2/dir3
     * @param zipName zip条目名称，如：1.txt，如果为空则为文件本身的名称
     */
    addLocalFile(localPath: string, zipPath?: string, zipName?: string): void {
        if (fs.existsSync(localPath)) {
            if (zipPath) {
                zipPath = zipPath.split('\\').join('/');
                if (zipPath.charAt(zipPath.length - 1) !== '/') {
                    zipPath += '/';
                }
            } else {
                zipPath = '';
            }
            const p = localPath.split('\\').join('/').split('/').pop();

            if (zipName) {
                this.addFile(zipPath + zipName, localPath);
            } else {
                this.addFile(zipPath + p, localPath);
            }
        } else {
            throw new Error(`文件:${localPath}不存在`);
        }
    }
    /**
     * 添加本地目录到zip文件
     * @param localPath 本地目录的路径
     * @param zipPath 添加zip文件内的路径,如果为空则添加到根目录，否则添加到指定目录下,如：dir1或dir1/dir2
     * @param filter 过滤文件选项,正则表达式或函数((file:string) => boolean)
     */
    addLocalFolder(localPath: string, zipPath?: string, filter?: RegExp | Function): void {
        if (filter === undefined) {
            filter = function () {
                return true;
            };
        } else if (filter instanceof RegExp) {
            filter = function(f: RegExp) {
                return function (filename) {
                    return f.test(filename);
                };
            }(filter);
        }

        if (zipPath) {
            zipPath = zipPath.split('\\').join('/');
            if (zipPath.charAt(zipPath.length - 1) !== '/') {
                zipPath += '/';
            }
        } else {
            zipPath = '';
        }
        // normalize the path first
        localPath = Path.normalize(localPath);
        localPath = localPath.split('\\').join('/');
        if (localPath.charAt(localPath.length - 1) !== '/') {
            localPath += '/';
        }

        if (fs.existsSync(localPath)) {

            const items = FSUtils.findFiles(localPath), self = this;

            if (items.length) {
                items.forEach((path) => {
                    const p = path.split('\\').join('/').replace(new RegExp(localPath.replace(/(\(|\))/g, '\\$1'), 'i'), '');
                    if ((<Function>filter)(p)) {
                        if (p.charAt(p.length - 1) !== '/') {
                            this.addFile(zipPath + p, path);
                        } else {
                            this.zip.folder(zipPath + p);
                        }
                    }
                });
            }
        } else {
            throw new Error('目录不存在');
        }
    }
    /**
     * 移除zip条目
     * @param entryName zip条目名称，如：dir1/dir2/file1.txt或file2.json
     */
    removeEntry(entryName: string) {
        this.zip.remove(entryName);
    }
    /**
     * 保存zip文件
     * @param callback 回调处理
     */
    save(callback?: (error: any, state: boolean) => void, updateCallback?: (metaData: any) => void): void {
        this.saveTo(this.zipFile, callback, updateCallback);
    }
    /**
     * 保存zip文件
     * @param file 目标路径，如:c:\1.zip
     * @param callback 回调处理
     * @param updateCallback 回调处理
     */
    saveTo(file: string, callback?: (error: any, status: boolean) => void, updateCallback?: (metaData: any) => void): void {
        const overwrite = true;
        const options = {type: 'nodebuffer', encodeFileName: fileName => fileName};
        const _callback = (error: any, status: boolean) => {
            if (callback) {
                callback(error, status);
            }
        };
        this.zip.generateAsync(options, updateCallback)
        .then((data) => {
            FSUtils.writeFileToAsync(file, data, overwrite, (error, status) => {
                _callback(error, status);
            });
        })
        .catch(error => {
            _callback(error, false);
        });
    }
    /**
     * 保存zip文件为base64字符串
     * @param callback 回调处理
     * @param updateCallback 回调处理
     */
    saveToBase64String(callback?: (error: any, base64String: string) => void, updateCallback?: (metaData: any) => void) {
        const options = {type: 'base64', encodeFileName: fileName => fileName};
        const _callback = (error: any, base64: string) => {
            if (callback) {
                callback(error, base64);
            }
        };
        this.zip.generateAsync(options, updateCallback)
        .then(function (base64) {
            _callback(null, base64);
        })
        .catch(error => {
            _callback(error, null);
        });
    }
    /**
     * 解压zip文件到指定目录
     * @param targetPath 目标目录
     * @param callback 解压回调处理
     * @param overwrite 标识是否覆盖存在的文件,默认false不覆盖存在的文件
     */
    extractAllToAsync(targetPath: string, callback?: (error: any, status: boolean) => void, overwrite?: boolean): void {
        overwrite = typeof(overwrite) === 'undefined' ? false : overwrite;
        const _callback = (error, status: boolean) => {
            if (callback) {
                callback(error, status);
            }
        };
        const entries = this.getEntries();
        if (entries && entries.length > 0) {
            const extract = (index: number) => {
                if (index === entries.length) {
                    _callback(null, true);
                    return;
                }

                try {
                    if (!entries[index].dir) {
                        this.readAsFileAsync(entries[index], (error, data) => {
                            if (!error) {
                                const entryName = entries[index].name;
                                const target = this.sanitize(targetPath, entryName);
                                FSUtils.writeFileToAsync(target, data, overwrite, (err, status) => {
                                    if (status) {
                                        extract(index + 1);
                                    } else {
                                        _callback(err, false);
                                    }
                                });
                            } else {
                                _callback(error, false);
                            }
                        });
                    } else {
                        const entryName = entries[index].name;
                        const target = this.sanitize(targetPath, entryName);
                        FSUtils.mkdirSync(target);
                        extract(index + 1);
                    }
                } catch (ex) {
                    _callback(ex, false);
                }
            };
            extract(0);
        }
    }
    /**
     * 解压指定zip条目到指定目录
     * @param entryName zip条目名称，如：dir1/dir2/file1.txt或file2.json
     * @param targetPath 目标目录
     * @param callback 回调方法
     * @param maintainEntryPath 标识是否保持zip条目的路径，默认为true
     * @param overwrite 标识如果文件存在是否覆盖存在的文件，默认为false
     */
    extractEntryTo(entryName: string, targetPath: string, callback?: (error: any, file: string) => void,
    maintainEntryPath?: boolean, overwrite?: boolean): void {
        overwrite = typeof(overwrite) === 'undefined' ? false : overwrite;
        maintainEntryPath = typeof(maintainEntryPath) === 'undefined' ? true : maintainEntryPath;
        const _callback = (error, file) => {
            if (callback) {
                callback(error, file);
            }
        };
        try {
            const target = this.sanitize(targetPath, maintainEntryPath ? entryName : Path.basename(entryName));
            this.readAsFileAsync(entryName, (error, data) => {
                if (!error) {
                    FSUtils.writeFileToAsync(target, data, overwrite, (err, status) => {
                        _callback(err, status ? target : null);
                    });
                } else {
                    _callback(error, null);
                }
            });
        } catch (ex) {
            _callback(ex, status);
        }
    }
    /**
     * 获取所有的zip条目信息
     */
    getEntries(): any[] {
        const enties = [];
        if (this.zip.hasOwnProperty('files')) {
            for (const key in this.zip.files) {
                if (this.zip.files.hasOwnProperty(key)) {
                    const file = this.zip.files[key];
                    enties.push(file);
                }
            }
        }
        return enties;
    }
    /**
     * 获取指定名称的zip条目
     * @param entryName zip条目名称，如：dir1/dir2/file1.txt或file2.json
     */
    getEntry(entryName: string): any {
        return this.zip.file(entryName);
    }
    /**
     * 异步读取指定zip条目的文本数据
     * @param entry zip条目(如:dir1/dir2/file.txt)
     * @param callback 回调处理
     */
    readAsTextAsync(entryName: string, callback: (error, text: string) => void): void {
        const zipEntry = this.zip.file(entryName);
        const _callback = (error, text?: string) => {
            if (callback) {
                callback(error, text);
            }
        };
        return zipEntry.async('nodebuffer')
        .then(data => {
            try {
                // 解码buffer数据为字符串
                const str = CommonUtils.decodeBufferToString(data);
                _callback(null, str);
            } catch (ex) {
                _callback(ex);
            }
        }).catch(error => {
            _callback(error);
        });
    }
    /**
     * 异步读取指定zip条目的数据
     * @param entryName zip条目(如:dir1/dir2/file.txt)
     * @param callback 回调处理
     */
    readAsBase64Async(entryName: any, callback: (error, data: string) => void): void {
        const _callback = (error, base64?: string) => {
            if (callback) {
                callback(error, base64);
            }
        };
        let zipEntry: any;
        if (typeof(entryName) === 'string') {
            zipEntry = this.zip.file(entryName);
        } else {
            zipEntry = entryName;
        }
        return zipEntry.async('base64')
        .then(data => _callback(null, data))
        .catch(error => _callback(error));
    }
    /**
     * 异步读取指定zip条目的数据
     * @param entryName zip条目(如:dir1/dir2/file.txt)
     * @param callback 回调处理
     */
    readAsFileAsync(entryName: any, callback: (error, data: Buffer) => void): void {
        const _callback = (error, data?: Buffer) => {
            if (callback) {
                callback(error, data);
            }
        };

        let zipEntry: any;
        if (typeof(entryName) === 'string') {
            zipEntry = this.zip.file(entryName);
        } else {
            zipEntry = entryName;
        }
        return zipEntry.async('nodebuffer')
        .then(data => _callback(null, data))
        .catch(error => _callback(error));
    }
    /**
     * 加入本地文件到zip
     * @param entryName zip条目名称
     * @param localPath 本地文件路径
     */
    private addFile (entryName: string, localPath: string) {
        this.zip.file(entryName, FSUtils.readLocalFile(localPath));
    }
    private sanitize(prefix: string, name: string) {
        prefix = Path.resolve(Path.normalize(prefix));
        const parts = name.split('/');
        for (let i = 0, l = parts.length; i < l; i++) {
            const path = Path.normalize(Path.join(prefix, parts.slice(i, l).join(Path.sep)));
            if (path.indexOf(prefix) === 0) {
                return path;
            }
        }
        return Path.normalize(Path.join(prefix, Path.basename(name)));
    }
}
